home *** CD-ROM | disk | FTP | other *** search
/ The Fatted Calf / The Fatted Calf.iso / Unix / CNews / Source / relay / history.c < prev    next >
Encoding:
C/C++ Source or Header  |  1992-03-17  |  9.0 KB  |  382 lines

  1. /*
  2.  * history file bashing
  3.  *
  4.  * B 2.10.3+ rnews puts out a leading space before received
  5.  * time if the article contains an Expires: header; tough.
  6.  * C news does this right instead of compatibly.
  7.  *
  8.  * The second history field is really two: time-received and Expires: value,
  9.  * separated by a tilde.  This is an attempt at partial compatibility with
  10.  * B news, in that C expire can cope with B news history files.
  11.  *
  12.  * There is no point to storing seek offsets in network byte order in the
  13.  * dbm file, since dbm files are machine-dependent and so can't be shared
  14.  * by dissimilar machines anyway.
  15.  */
  16.  
  17. #include <stdio.h>
  18. #include <stdlib.h>
  19. #include <string.h>        /* for memcpy */
  20. #include <errno.h>
  21. #include "fixerrno.h"
  22. #include <sys/types.h>
  23. #include "libc.h"
  24. #include "news.h"
  25. #include "config.h"
  26. #include "dbz.h"
  27. #include "fgetmfs.h"
  28. #include "headers.h"
  29. #include "article.h"
  30. #include "history.h"
  31. #include "msgs.h"
  32.  
  33. #define HISTNAME "history"    /* name of the history file in $NEWSCTL */
  34. #define FIELDSEP '\t'
  35. #define SUBFIELDSEP '~'
  36.  
  37. /* give 0 & 2 pretty, SVIDish names */
  38. #ifndef SEEK_SET
  39. #define SEEK_SET 0
  40. #define SEEK_END 2
  41. #endif
  42.  
  43. /* private data */
  44. static FILE *fp = NULL;
  45. static char *filename;        /* absolute name of the ascii history file */
  46. static boolean writable;
  47.  
  48. /* libdbm imports */
  49. extern int dbminit(), store();
  50. extern datum fetch();
  51.  
  52. /* other imports */
  53. extern void prefuse();
  54. extern boolean okrefusal;    /* flag from command line */
  55.  
  56. /* forward decls */
  57. FORWARD datum getposhist();
  58. FORWARD void mkhistent(), sanitise(), subsanitise();
  59. void decline();
  60.  
  61. STATIC void
  62. histname()
  63. {
  64.     if (filename == NULL)
  65.         filename = strsave(ctlfile(HISTNAME));
  66. }
  67.  
  68. /*
  69.  * open the history files: ascii first, then dbm.
  70.  * Try a+ mode first, then r mode, as dbm(3) does nowadays,
  71.  * so that this routine can be used by any user to read history files.
  72.  */
  73. STATIC boolean
  74. openhist()
  75. {
  76.     histname();
  77.     if (fp == NULL) {
  78.         if ((fp = fopenclex(filename, "a+")) != NULL)
  79.             writable = YES;
  80.         else if ((fp = fopenwclex(filename, "r")) != NULL)
  81.             writable = NO;
  82.         /* else fp==NULL and fopenwclex just complained */
  83.  
  84.         errno = 0;
  85.         if (fp != NULL && dbminit(filename) < 0) {
  86.             /*
  87.              * no luck.  dbm's dbminit will have just honked (on
  88.              * stdout, alas) but dbz's won't have, so bitch.
  89.              */
  90.             warning(
  91.         "database files for `%s' incomprehensible or unavailable",
  92.                 filename);
  93.             (void) nfclose(fp);    /* close ascii file */
  94.             fp = NULL;        /* and mark it closed */
  95.         }
  96.     }
  97.     return fp != NULL;
  98. }
  99.  
  100. STATIC datum
  101. getposhist(msgid)        /* return seek offset of history entry */
  102. char *msgid;
  103. {
  104.     register char *clnmsgid;
  105.     datum msgidkey, keypos;
  106.  
  107.     msgidkey.dptr = NULL;
  108.     msgidkey.dsize = 0;
  109.     if (!openhist())
  110.         return msgidkey;
  111.     clnmsgid = strsave(msgid);
  112.     sanitise(clnmsgid);
  113.     msgidkey.dptr = clnmsgid;
  114.     msgidkey.dsize = strlen(clnmsgid) + SIZENUL;
  115.     keypos = dbzfetch(msgidkey);        /* offset into ascii file */
  116.     free(clnmsgid);
  117.     return keypos;
  118. }
  119.  
  120. boolean
  121. alreadyseen(msgid)        /* return true if found in the data base */
  122. char *msgid;
  123. {
  124.     datum posdatum;
  125.  
  126.     posdatum = getposhist(msgid);
  127.     return posdatum.dptr != NULL;
  128. }
  129.  
  130. char *                /* NULL if no history entry; else malloced */
  131. gethistory(msgid)        /* return existing history entry, if any */
  132. char *msgid;
  133. {
  134.     long pos = 0;
  135.     datum posdatum;
  136.  
  137.     posdatum = getposhist(msgid);
  138.     if (posdatum.dptr != NULL && posdatum.dsize == sizeof pos) {
  139.         static char *histent = NULL;
  140.  
  141.         (void) memcpy((char *)&pos, posdatum.dptr, sizeof pos); /* align */
  142.         nnfree(&histent);
  143.         if (fseek(fp, pos, SEEK_SET) != -1 &&
  144.             (histent = fgetms(fp)) != NULL)
  145.             return histent;        /* could note move from EOF */
  146.     }
  147.     return NULL;
  148. }
  149.  
  150. /*
  151.  * Return a pointer to the "files" field of a history entry.
  152.  * Side-effect: trims \n from the history entry.
  153.  */
  154. char *
  155. findfiles(histent)
  156. char *histent;
  157. {
  158.     register char *tabp;
  159.  
  160.     trim(histent);
  161.     /* find start of 2nd field (arrival~expiry) */
  162.     tabp = strchr(histent, FIELDSEP);
  163.     if (tabp == NULL)
  164.         return NULL;                /* mangled entry */
  165.     /* find start of 3rd field (files list) */
  166.     else if ((tabp = strchr(tabp + 1, FIELDSEP)) == NULL)
  167.         return NULL;            /* cancelled or expired art. */
  168.     else
  169.         return tabp + 1;
  170. }
  171.  
  172. /*
  173.  * Generate a history entry from art.
  174.  * The history entry will have tabs and newlines deleted from the
  175.  * interior of fields, to keep the file format sane.
  176.  * Optionally print the start of an "accepted" log file line (no \n)
  177.  * (transmit() prints site names).
  178.  */
  179. void
  180. history(art, startlog)
  181. register struct article *art;
  182. boolean startlog;
  183. {
  184.     register char *msgid, *expiry;
  185.     time_t now;
  186.  
  187.     if (!msgidok(art))        /* complains in log if unhappy */
  188.         return;            /* refuse to corrupt history */
  189.     msgid = strsave(nullify(art->h.h_msgid));
  190.     sanitise(msgid);    /* RFC 1036 forbids whitespace in msg-ids */
  191.     expiry = strsave(nullify(art->h.h_expiry));
  192.     sanitise(expiry);
  193.     subsanitise(expiry);
  194.  
  195.     if (startlog) {
  196.         timestamp(stdout, &now);
  197.         if (printf(" %s + %s", sendersite(nullify(art->h.h_path)),
  198.             msgid) == EOF)
  199.             fulldisk(art, "stdout");
  200.     } else
  201.         now = time(&now);
  202.     if (!openhist())
  203.         art->a_status |= ST_DROPPED|ST_NEEDATTN;    /* serious */
  204.     else if (!writable) {
  205.         (void) fprintf(stderr, "%s: no write permission on `%s'\n",
  206.             progname, filename);
  207.         art->a_status |= ST_DROPPED|ST_NEEDATTN;    /* serious */
  208.     } else if (fseek(fp, 0L, SEEK_END) == -1) {
  209.         /* could avoid fseek if still at EOF */
  210.         warning("can't seek to end of `%s'", filename);
  211.         art->a_status |= ST_DROPPED;
  212.     } else
  213.         mkhistent(art, msgid, now, expiry);
  214.     free(msgid);
  215.     free(expiry);
  216. }
  217.  
  218. void
  219. decline(art)                    /* mark art as undesirable */
  220. struct article *art;
  221. {
  222.     art->a_status |= ST_REFUSED|(okrefusal? 0: ST_DROPPED);
  223. }
  224.  
  225. char *
  226. ismsgidbad(msgid)                /* if bad, return error */
  227. register char *msgid;
  228. {
  229.     if (msgid == NULL || msgid[0] == '\0')
  230.         return "missing Message-ID";
  231.     else if (strchr(msgid, '@') == NULL)
  232.         return "no @ in Message-ID";
  233.     else if (strchr(msgid, ' ') != NULL || strchr(msgid, '\t') != NULL)
  234.         return "whitespace in Message-ID";
  235.     else if (msgid[0] != '<' || msgid[strlen(msgid)-1] != '>')
  236.         return "Message-ID not bracketed by <>";
  237.     else
  238.         return NULL;
  239. }
  240.  
  241. int
  242. msgidok(art)                    /* if bad, complain in log */
  243. register struct article *art;
  244. {
  245.     register char *err = ismsgidbad(art->h.h_msgid);
  246.  
  247.     if (err == NULL)
  248.         return YES;
  249.     else {
  250.         prefuse(art);
  251.         (void) fputs(err, stdout);
  252.         decline(art);
  253.         return NO;
  254.     }
  255. }
  256.  
  257. /*
  258.  * Internal interface to generate a history file entry,
  259.  * assuming all sanity checking has been done already.
  260.  * Record the (msgid, position) pair in the data base.
  261.  *
  262.  * The fflush is crash-proofing.
  263.  */
  264. STATIC void
  265. mkhistent(art, msgid, now, expiry)
  266. register struct article *art;
  267. char *msgid, *expiry;
  268. time_t now;
  269. {
  270.     long pos;
  271.     datum msgidkey, posdatum;
  272.  
  273.     pos = ftell(fp);  /* get seek ptr for dbm; could keep track instead */
  274.     if (fprintf(fp, "%s%c%ld%c%s", msgid, FIELDSEP, now, SUBFIELDSEP, expiry)
  275.         == EOF)
  276.         fulldisk(art, filename);
  277.     /* don't write 3rd field for cancelled but unseen articles */
  278.     if (art->a_files != NULL && art->a_files[0] != '\0')
  279.         if (fprintf(fp, "%c%s", FIELDSEP, art->a_files) == EOF)
  280.             fulldisk(art, filename);
  281.     (void) putc('\n', fp);
  282.     if (fflush(fp) == EOF)
  283.         fulldisk(art, filename);
  284.  
  285.     msgidkey.dptr = msgid;
  286.     msgidkey.dsize = strlen(msgid) + SIZENUL;
  287.     posdatum.dptr = (char *)&pos;
  288.     posdatum.dsize = sizeof pos;
  289.     if (dbzstore(msgidkey, posdatum) < 0)
  290.         fulldisk(art, filename);
  291. }
  292.  
  293. /*
  294.  * Turn \n & FIELDSEP into ' ' in s.
  295.  */
  296. STATIC void
  297. sanitise(s)
  298. register char *s;
  299. {
  300.     for (; *s != '\0'; ++s)
  301.         if (*s == FIELDSEP || *s == '\n')
  302.             *s = ' ';
  303. }
  304.  
  305. /*
  306.  * Turn SUBFIELDSEP into ' ' in s.
  307.  */
  308. STATIC void
  309. subsanitise(s)
  310. register char *s;
  311. {
  312.     for (; *s != '\0'; ++s)
  313.         if (*s == SUBFIELDSEP)
  314.             *s = ' ';
  315. }
  316.  
  317. /*
  318.  * Generate a fake history file entry, given a message-id, an Expires:
  319.  * value, and a "file" list ("net.foo/123").
  320.  */
  321. statust
  322. fakehist(fkmsgid, fkexpiry, fkfiles)
  323. char *fkmsgid, *fkexpiry, *fkfiles;
  324. {
  325.     struct article art;
  326.  
  327.     artinit(&art);
  328.     art.h.h_msgid = fkmsgid;
  329.     art.h.h_expiry = fkexpiry;
  330.     art.a_files = fkfiles;
  331.     history(&art, STARTLOG);
  332.     return art.a_status;
  333. }
  334.  
  335. /*
  336.  * Append "group/artnumstr" to the file list in *art.
  337.  */
  338. void
  339. histupdfiles(art, group, artnumstr)
  340. register struct article *art;
  341. register char *group;
  342. register char *artnumstr;
  343. {
  344.     unsigned addlen = strlen(group) + STRLEN(SFNDELIM) +
  345.         strlen(artnumstr) + SIZENUL;
  346.  
  347.     art->a_filed = YES;            /* make a note */
  348.     if (art->a_files == NULL) {
  349.         art->a_files = nemalloc(addlen);
  350.         art->a_files[0] = '\0';
  351.     } else {
  352.         art->a_files = realloc(art->a_files, (unsigned)
  353.             strlen(art->a_files) + STRLEN(" ") + addlen);
  354.         if (art->a_files == NULL)
  355.             errunlock("can't grow a_files", "");
  356.         (void) strcat(art->a_files, " ");
  357.     }
  358.     (void) strcat(art->a_files, group);    /* normal case */
  359.     (void) strcat(art->a_files, SFNDELIM);
  360.     (void) strcat(art->a_files, artnumstr);
  361. }
  362.  
  363. statust
  364. closehist()
  365. {
  366.     register statust status = ST_OKAY;
  367.  
  368.     if (fp != NULL) {
  369.         /* dbmclose is only needed by dbz, to flush statistics to disk */
  370.         if (dbmclose() < 0) {
  371.             warning("error closing dbm history file", "");
  372.             status |= ST_DROPPED;
  373.         }
  374.         if (nfclose(fp) == EOF) {
  375.             warning("error closing history file", "");
  376.             status |= ST_DROPPED;
  377.         }
  378.         fp = NULL;        /* mark file closed */
  379.     }
  380.     return status;
  381. }
  382.